package org.netwia.networks;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.lang3.StringUtils;
import org.netwia.utils.DomainUtils;
import org.netwia.utils.StartStopService;
import org.w3c.dom.NodeList;

/**
 * Class which is containing static utility methods for networks.
 * 
 * @author radek.hecl
 *
 */
public final class NetworkUtils {

    /**
     * To prevent construction.
     */
    private NetworkUtils() {
    }
    
    /**
     * Creates network from the given xml node.
     * 
     * @param node xml node from which the network is created
     * @param typeRef any instance of object for the reference type
     * @param environment environment for the network, these objects can be injected into the network
     * @return created network
     */
    public static <T> Network<T> createNetworkFromXml(org.w3c.dom.Node node, T typeRef, Map<String, ? extends Object> environment) {
        XPath xpath = XPathFactory.newInstance().newXPath();
        Network.Builder<T> res = new Network.Builder<T>();
        try {
            XPathExpression nodesExpr = xpath.compile("nodes/node");
            XPathExpression connectionsExpr = xpath.compile("connections/connection");
            
            XPathExpression nameExpr = xpath.compile("name/text()");
            XPathExpression inputNameExpr = xpath.compile("inputProvider/name/text()");
            XPathExpression inputTargetExpr = xpath.compile("inputProvider/target/text()");
            XPathExpression outputSourceExpr = xpath.compile("outputListener/source/text()");
            
            // parse nodes
            NodeList nodesNodes = (NodeList) nodesExpr.evaluate(node, XPathConstants.NODESET);
            for (int i = 0; i < nodesNodes.getLength(); ++i) {
                org.w3c.dom.Node n = nodesNodes.item(i);
                @SuppressWarnings("unchecked")
                Node<T> networkNode = (Node<T>)DomainUtils.buildObjectFromXml(n, environment);
                res.addNode(nameExpr.evaluate(n), networkNode);
            }
            
            // parse connections
            NodeList connectionNodes = (NodeList) connectionsExpr.evaluate(node, XPathConstants.NODESET);
            for (int i = 0; i < connectionNodes.getLength(); ++i) {
                org.w3c.dom.Node n = connectionNodes.item(i);
                Object connection = DomainUtils.buildObjectFromXml(n, environment);
                String inputName = inputNameExpr.evaluate(n);
                String inputTarget = inputTargetExpr.evaluate(n);
                String outputSource = outputSourceExpr.evaluate(n);
                if (StringUtils.isNotEmpty(inputName)) {
                    @SuppressWarnings("unchecked")
                    InputProvider<T> input = (InputProvider<T>)connection;
                    res.addInput(inputTarget, inputName, input);
                }
                if (StringUtils.isNotEmpty(outputSource)) {
                    @SuppressWarnings("unchecked")
                    OutputListener<T> output = (OutputListener<T>)connection;
                    res.addOutput(outputSource, output);
                    
                }
            }
        } catch (XPathExpressionException e) {
            throw new RuntimeException(e);
        }

        return res.build();
    }

    /**
     * Creates network environment from the given xml node.
     * 
     * @param node xml node from which the network is created
     * @param typeRef any instance of object for the reference type
     * @param environment initial environment, will be merged into the result
     * @return created network environment
     */
    public static <T> NetworkEnvironment<T> createNetworkEnvironmentFromXml(
            org.w3c.dom.Node node, T typeRef, Map<String, ? extends Object> environment) {
        XPath xpath = XPathFactory.newInstance().newXPath();
        NetworkEnvironment.Builder<T> res = new NetworkEnvironment.Builder<T>();
        try {
            XPathExpression environmentExpr = xpath.compile("environment");
            XPathExpression metaDataExpr = xpath.compile("metaData/property");
            XPathExpression networkExpr = xpath.compile("network");
            XPathExpression nameExpr = xpath.compile("name");
            XPathExpression valueExpr = xpath.compile("value");

            org.w3c.dom.Node envNode = (org.w3c.dom.Node)environmentExpr.evaluate(node, XPathConstants.NODE);
            Map<String, Object> resEnvironment = DomainUtils.buildEnvironmentFromXml(envNode, environment);
            res.setEnvironment(resEnvironment);
            org.w3c.dom.Node ntwNode = (org.w3c.dom.Node)networkExpr.evaluate(node, XPathConstants.NODE);
            res.setNetwork(createNetworkFromXml(ntwNode, typeRef, resEnvironment));
            
            // parse nodes
            NodeList metaDataNodes = (NodeList) metaDataExpr.evaluate(node, XPathConstants.NODESET);
            for (int i = 0; i < metaDataNodes.getLength(); ++i) {
                org.w3c.dom.Node n = metaDataNodes.item(i);
                String name = nameExpr.evaluate(n);
                String value = valueExpr.evaluate(n);
                res.addMetaData(name, value);
            }
            
        } catch (XPathExpressionException e) {
            throw new RuntimeException(e);
        }

        return res.build();
    }
    
    /**
     * Creates network processor for the best match of the specified environment.
     * 
     * @param environment environment for which the processor is created
     * @return created processor
     */
    public static <T> StartStopService createNetworkProcessor(NetworkEnvironment<T> environment) {
        AsyncSimpleNetworkProcessor.Builder<T> res = new AsyncSimpleNetworkProcessor.Builder<T>();
        res.setNetwork(environment.getNetwork());
        for (String name : environment.getMetaData().keySet()) {
            if (name.equals("processor.childServices")) {
                List<String> services = Arrays.asList(environment.getMetaData().get(name).split(", ?"));
                for (String service : services) {
                    if (StringUtils.isEmpty(service)) {
                        continue;
                    }
                    res.addChildService((StartStopService)environment.getEnvironment().get(service));
                }
            }
        }
        return res.build();
    }
    
}
